/*
 *  Copyright (C) <2009>  Pavel Vakeš <pavel.vakes@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <mail_checker.h>
#include <KDebug>

MailChecker::MailChecker(Account * account) : QObject(),
	actual_action(CLOSE_CONNECTION), certificate_details_dialog(0), time_to_check(0), check_messages(-1), check_error(false)
{
	this->account = account;

	connect(&socket, SIGNAL( stateChanged(QAbstractSocket::SocketState) ), this, SLOT( socketStateChanged(QAbstractSocket::SocketState) ), Qt::DirectConnection);
	connect(&socket, SIGNAL( error(QAbstractSocket::SocketError) ), this, SLOT( socketError(QAbstractSocket::SocketError) ), Qt::DirectConnection);
	connect(&socket, SIGNAL( readyRead() ), this, SLOT( readData() ));
	connect(&socket, SIGNAL( encrypted() ), this, SLOT( readData() ));
}

MailChecker::~MailChecker()
{
}

void MailChecker::startPeriodicallyCheck() {
	if (account->check_after_run) {
		this->startCheck();
	}
	else {
		time_to_check = account->interval;
		check_timer.start(60000, this);
	}
	emit timeToCheckChanged(time_to_check);
}

void MailChecker::stopCheck() {
	socket.close();
	check_timer.stop();
}

void MailChecker::timerEvent(QTimerEvent *event)
{
	if (event->timerId() == check_timer.timerId()) {
		emit timeToCheckChanged(--time_to_check);
		if (time_to_check == 0) startCheck();
	}
	else if (event->timerId() == connection_timer.timerId()) {
		if (!socket.waitForConnected(100)) endCheck();
		connection_timer.stop();
	}
	else {
		QObject::timerEvent(event);
	}
}

void MailChecker::startCheck() {
	if (actual_action < CLOSE_CONNECTION) {
		socket.abort();
		endCheck();
	}

	check_timer.stop();
	time_to_check = 0;
	emit timeToCheckChanged(time_to_check);

	writeData(OPEN_CONNECTION);
}

void MailChecker::writeData(Action action) {
	QByteArray data;

	actual_action = action;
	read_data.clear();

	emit actionChanged(actual_action);

	switch (actual_action) {
		case OPEN_CONNECTION:
			if (account->ssl) {
				socket.setProtocol(QSsl::AnyProtocol);
				socket.setPeerVerifyMode(QSslSocket::QueryPeer);
				socket.connectToHostEncrypted(account->host, account->port);
			}
			else socket.connectToHost(account->host, account->port);
		break;

		case VERIFY_CERT: {
			bool trusted_certificate = false;

			for (int i = 0; i < account->certificates.size(); ++i) {
				if (QSslCertificate(account->certificates.at(i), QSsl::Pem) == socket.peerCertificate()) {
					trusted_certificate = true;
					break;
				}
			}

			if (trusted_certificate) writeData(VERIFY_USER);
			else showCertificateDetailsDialog(socket.peerCertificate());
		} break;

		case VERIFY_USER:
			if (account->protocol == "POP3") {
				data.append("USER ");
				data.append(account->username.toAscii());
			}
			else if (account->protocol == "IMAP4") {
				data.append("001 LOGIN ");
				data.append(account->username.toAscii());
				data.append(" ");
				data.append(account->password.toAscii());
			}
		break;

		case VERIFY_PASS:
			data.append("PASS ");
			data.append(account->password.toAscii());
		break;

		case GET_STAT:
			if (account->protocol == "POP3") {
				data.append("STAT");
			}
			else if (account->protocol == "IMAP4") {
				data.append("002 STATUS INBOX (UNSEEN)");
			}
		break;

		case CLOSE_CONNECTION:
			socket.close();
		break;
	}

	if (!data.isEmpty()) {
		data.append("\r\n");
		socket.write(data);
	}
}

void MailChecker::readData() {
	if (actual_action == OPEN_CONNECTION && account->ssl && (!socket.isEncrypted() || socket.bytesAvailable() == 0)) return;

	read_data.append(socket.readAll());

	if (read_data.contains("OK") && read_data.endsWith("\n")) {
		switch (actual_action) {
			case OPEN_CONNECTION:
				if (account->ssl) writeData(VERIFY_CERT);
				else writeData(VERIFY_USER);
			break;

			case VERIFY_CERT:
			break;

			case VERIFY_USER:
				if (account->protocol == "POP3") {
					writeData(VERIFY_PASS);
				}
				else if (account->protocol == "IMAP4") {
					writeData(GET_STAT);
				}
			break;

			case VERIFY_PASS:
				writeData(GET_STAT);
			break;

			case GET_STAT:
			{
				if (account->protocol == "POP3") {
					check_messages = read_data.split(' ').at(1).toInt();
				}
				else if (account->protocol == "IMAP4") {
					check_messages = read_data.split(' ').at(4).split(')').at(0).toInt();
				}

				writeData(CLOSE_CONNECTION);
			}
			break;

			case CLOSE_CONNECTION:
			break;
		}
	}
	else if ((read_data.contains("ERR") || read_data.contains("BAD")) && read_data.endsWith("\n")) {
		socket.close();
	}
}

void MailChecker::socketStateChanged(QAbstractSocket::SocketState socketState) {
	switch (socketState) {
		case QAbstractSocket::HostLookupState:
			connection_timer.start(account->connection_timeout * 60000, this);
		break;

		case QAbstractSocket::UnconnectedState:
			connection_timer.stop();
			endCheck();
			actual_action = CLOSE_CONNECTION;
		break;

		default: break;
	}
}

void MailChecker::socketError(QAbstractSocket::SocketError) {
	endCheck(socket.errorString());
	if (socket.isOpen()) socket.close();
}

void MailChecker::endCheck(const QString error) {
	if (check_timer.isActive()) return;

	if (actual_action < CLOSE_CONNECTION || !error.isNull()) {
		check_messages = 0;

		if (!error.isNull()) check_error = error;
		else switch (actual_action) {
			case VERIFY_USER: case VERIFY_PASS:
				check_error = "Authentication failed";
			break;

			case GET_STAT:
				check_error = "Messages reading failed";
			break;

			default:
			break;
		}

		emit endCheckError(check_error);
	}
	else {
		check_error = QString::null;
		emit endCheckMessages(check_messages);
	}

	time_to_check = account->interval;
	emit timeToCheckChanged(time_to_check);
	check_timer.start(60000, this);

	closeCertificateDetailsDialog();
}

void MailChecker::runMailClient() {
	if (!account->mail_client.isEmpty()) {
		QStringList args = account->mail_client.split(" ");
		KProcess::startDetached(args.takeFirst(), args);
	}
}

QString MailChecker::accountName() {
	return account->name;
}

int MailChecker::timeToCheck() {
	return time_to_check;
}

int MailChecker::messages() {
	return (check_messages > 0 ? check_messages : 0);
}

QString MailChecker::checkError() {
	return check_error;
}

void MailChecker::showCertificateDetailsDialog(const QSslCertificate &certificate)
{
	certificate_details_dialog = new CertificateDetailsDialog(certificate);
	certificate_details_dialog->setWindowTitle("Detail of the certificate for " + this->accountName());

	connect(certificate_details_dialog, SIGNAL( accepted() ), this, SLOT( acceptCertificate() ));
	connect(certificate_details_dialog, SIGNAL( rejected() ), this, SLOT( rejectCertificate() ));

	certificate_details_dialog->show();
}

void MailChecker::closeCertificateDetailsDialog()
{
	if (certificate_details_dialog && certificate_details_dialog->isVisible()) {
		certificate_details_dialog->close();
		delete certificate_details_dialog;
		certificate_details_dialog = 0;
	}
}

void MailChecker::acceptCertificate() {
	if (actual_action == VERIFY_CERT) {
		account->certificates.append(socket.peerCertificate().toPem());
		emit certificateAccepted(account);
		writeData(VERIFY_USER);
	}
}

void MailChecker::rejectCertificate() {
	if (actual_action == VERIFY_CERT) {
		endCheck("Certificate rejected.");
		if (socket.isOpen()) socket.close();
	}
}
